package de.tum.in.eist.poll.server;

import java.util.List;

import javax.jdo.PersistenceManager;

import javax.jdo.Query;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

import de.tum.in.eist.poll.client.PollService;
import de.tum.in.eist.poll.client.UsedAddress;
import de.tum.in.eist.poll.client.Exceptions.ArgumentIsNullException;
import de.tum.in.eist.poll.client.Exceptions.DatabaseFailureException;
import de.tum.in.eist.poll.client.Exceptions.InvalidCharacterInputException;
import de.tum.in.eist.poll.client.Model.Credential;
import de.tum.in.eist.poll.client.Model.Feedback;
import de.tum.in.eist.poll.client.Model.Lecture;
import de.tum.in.eist.poll.client.Model.Rating;

@SuppressWarnings("serial")
public class PollServiceImpl extends RemoteServiceServlet implements
		PollService {
	
	private int revision = 1;

	public boolean checkCredentials(String user, String password) 
			throws DatabaseFailureException {
		
		PersistenceManager pm = DBUtil.get().getPersistenceManager();
		Query query = pm.newQuery(Credential.class);

        try {
            @SuppressWarnings("unchecked")
			List<Credential> credRes = (List<Credential>) query.execute();
            
            if (!credRes.isEmpty()) {
            	
            	for (Credential c : credRes) {
            		if (c.getUsername().compareTo(user) == 0 &&
            				c.getPassword().compareTo(password) == 0)
            			return true;
            	}
            	
            } else {
            	Credential c = new Credential("admin", "pw");
            	pm.makePersistent(c);
            }
        } catch (RuntimeException runExc) {
        	throw new DatabaseFailureException("Error with Database! Please try again later.");
        }
        finally {
            pm.close();
        }
		
		return false;
	}

	@SuppressWarnings("unchecked")
	public Lecture[] getLectures() 
		throws DatabaseFailureException {
		Lecture[] lectArr = null;

		PersistenceManager pm = DBUtil.get().getPersistenceManager();

        try {
            List<Lecture> lectRes = getLectureResList(pm, null);
            
            if (!lectRes.isEmpty()) {
            	lectArr = new Lecture[lectRes.size()];
            	List<UsedAddress> uaRes;
            	// List<Lecture> lectList = (List<Lecture>) pm.detachCopyAll(lectRes);
            	
            	int i = 0;
            	List<Feedback> fbRes;
            	for (Lecture l : lectRes) {
            		// Query used addresses
            		uaRes = getUsedAddressResList(pm, l);
            		
            		String ipAddr = getThreadLocalRequest().getRemoteAddr();
            		l.setAlreadyVoted(false);
            		
            		if (uaRes != null && uaRes.size() > 1 ) {
            			for (UsedAddress ua : uaRes) {
            				if (ua.getIpAddress().compareTo(ipAddr) == 0) {
            					l.setAlreadyVoted(true);
            					break;
            				}
            			}
            		}
            		
            		// Add Feedback objects to lecture
            		fbRes = getFeedbackResList(pm, l);
            		l.setFeedbacks(fbRes);
            		
            		lectArr[i++] = l;
            	}
            	
            	
            }
        } catch (InvalidCharacterInputException invalidCharExc) {
        	throw new DatabaseFailureException("Database Error!");
        } catch (ArgumentIsNullException argNullExc) {
        	throw new DatabaseFailureException("Database Error!");
        } 
        catch (RuntimeException runExc) {
        	throw new DatabaseFailureException("Error with Database! Please try again later.");
        }
        finally {
            pm.close();
        }
        
		return lectArr;
	}

	public Feedback[] getFeedbacks(Lecture l) 
			throws DatabaseFailureException, ArgumentIsNullException {
		Feedback[] fbArr = null;

		PersistenceManager pm = DBUtil.get().getPersistenceManager();
		
		try {
			List<Feedback> fbRes = getFeedbackResList(pm, l);
			fbArr = new Feedback[fbRes.size()];
			
			int i = 0;
			for (Feedback fb : fbRes) {
				fbArr[i++]  = fb;
			}
		} catch (InvalidCharacterInputException invCharExc) {
			throw new DatabaseFailureException("Database Error!");
		} catch (RuntimeException runExc) {
        	throw new DatabaseFailureException("Error with Database! Please try again later.");
        }
		finally {
			pm.close();
		}
		
		return fbArr;
	}
	
	public boolean pushFeedbackForLecture(Lecture l, Feedback f) 
		throws DatabaseFailureException, ArgumentIsNullException, 
			InvalidCharacterInputException {
		
		if (f == null || l == null) 
    		throw new ArgumentIsNullException("Error while handling request. Please try again.");
		
		String ipAddr = getThreadLocalRequest().getRemoteAddr();
		PersistenceManager pm = DBUtil.get().getPersistenceManager();
		
        try {
			List<UsedAddress> addressRes = getUsedAddressResList(pm, l);
        	
        	// Check ip
			for (UsedAddress ua : addressRes) {
				if (ua.getIpAddress().compareTo(ipAddr) == 0) {
					return false;
				}
			}
        	
        	pm.makePersistent(f);
        	l.getFeedbacks().add(f);
        	pm.makePersistent(new UsedAddress(ipAddr, l.getEncodedKey()));

        	revision++;
        } catch (RuntimeException runExc) {
        	throw new DatabaseFailureException("Error with Database! Please try again later.");
        }
        finally {
            pm.close();
        }
		
		return true;
	}
	
	public void addLecture(Lecture l) 
		throws DatabaseFailureException, ArgumentIsNullException, 
			InvalidCharacterInputException {
		
		if (l == null)
			throw new ArgumentIsNullException("Error while handling request. Please try again.");
		
		// Check for invalid input (throw exception)
		checkLectureObjectForInvalidChars(l);
		
		PersistenceManager pm = DBUtil.get().getPersistenceManager();
        try {        	
        	pm.makePersistent(l);
        	revision++;
        } catch (RuntimeException runExc) {
        	throw new DatabaseFailureException("Error with Database! Please try again later.");
        }
        finally {
            pm.close();
        }
		
	}
	
	public Rating[] getRatings(Lecture l) 
		throws DatabaseFailureException, ArgumentIsNullException, InvalidCharacterInputException {
		
		if (l == null)
			throw new ArgumentIsNullException("Error while handling request. Please try again.");
		
		// Check for invalid input (throw exception)
		checkLectureObjectForInvalidChars(l);
		
		Rating[] ratingArr;
		
		try {
			Feedback[] fbArr = this.getFeedbacks(l);		
			ratingArr = new Rating[fbArr.length];
		
			for (int i = 0; i < fbArr.length; i++) {
				ratingArr[i] = new Rating(fbArr[i].getRating());
			}
		} catch (RuntimeException runExc) {
        	throw new DatabaseFailureException("Error with Database! Please try again later.");
        }
		
		return ratingArr;
	}

	public void deleteLecture(Lecture l) 
		throws DatabaseFailureException, ArgumentIsNullException, 
			InvalidCharacterInputException {
		
		if (l == null)
			throw new ArgumentIsNullException("Error while handling request. Please try again.");
		
		// Check for invalid input (throw exception)
		checkLectureObjectForInvalidChars(l);
		
		PersistenceManager pm = DBUtil.get().getPersistenceManager();
		
		try {
			List<Feedback> fbRes = getFeedbackResList(pm, l);
			pm.deletePersistentAll(fbRes);
			List<Lecture> lectRes = getLectureResList(pm, l);
			pm.deletePersistentAll(lectRes);
			revision++;
		} catch (RuntimeException runExc) {
        	throw new DatabaseFailureException("Error with Database! Please try again later.");
        }
		finally {
			pm.close();
		}
	}

	
	public void openLecture(Lecture l) 
		throws DatabaseFailureException, ArgumentIsNullException, 
			InvalidCharacterInputException {
		
		if (l == null)
			throw new ArgumentIsNullException("Error while handling request. Please try again.");
		
		// Check for invalid input (throw exception)
		checkLectureObjectForInvalidChars(l);
		
		PersistenceManager pm = DBUtil.get().getPersistenceManager();
		
		try {
			Lecture lect = (Lecture)pm.getObjectById(Lecture.class, l.getEncodedKey());
			lect.setOpen(true);
			revision++;
		} catch (RuntimeException runExc) {
        	throw new DatabaseFailureException("Error with Database! Please try again later.");
        }
		finally {
			pm.close();
		}	
	}

	
	public void closeLecture(Lecture l) 
		throws DatabaseFailureException, ArgumentIsNullException, 
			InvalidCharacterInputException {
		
		if (l == null)
			throw new ArgumentIsNullException("Error while handling request. Please try again.");
		
		// Check for invalid input (throw exception)
		checkLectureObjectForInvalidChars(l);
		
		PersistenceManager pm = DBUtil.get().getPersistenceManager();
		
		try {
			Lecture lect = (Lecture)pm.getObjectById(Lecture.class, l.getEncodedKey());
			lect.setOpen(false);
			revision++;
		} catch (RuntimeException runExc) {
        	throw new DatabaseFailureException("Error with Database! Please try again later.");
        }
		finally {
			pm.close();
		}	
	}

	
	public int getUpdate() {
//		requests++;
//		if(requests%200==0){
//			System.out.println("add feedback");
//			lectures.get(0).getFeedbacks().add(new Feedback(5,"",""));
//			revision++;
//		}
		return revision;
	}
	
	private List<Lecture> getLectureResList(PersistenceManager pm, Lecture l) 
		throws DatabaseFailureException, InvalidCharacterInputException {
		
		Query lectQuery = pm.newQuery(Lecture.class);
		
		List<Lecture> lectRes;
		if (l != null) {
			lectQuery.setFilter("encodedKey == key");
			lectQuery.declareParameters("String key");
			
			lectRes = (List<Lecture>)lectQuery.execute(l.getEncodedKey());
		} else {
			lectRes = (List<Lecture>)lectQuery.execute();
		}		
		
		return lectRes;
	}
	
	private List<Feedback> getFeedbackResList(PersistenceManager pm, Lecture l) 
		throws DatabaseFailureException, ArgumentIsNullException, 
			InvalidCharacterInputException {
		
		Query fbQuery = pm.newQuery(Feedback.class);
		fbQuery.setFilter("lectureKey == key");
		fbQuery.declareParameters("String key");
		
		return (List<Feedback>)fbQuery.execute(l.getEncodedKey());
	}
	
	private List<UsedAddress> getUsedAddressResList(PersistenceManager pm, Lecture l) 
		throws DatabaseFailureException, ArgumentIsNullException, 
			InvalidCharacterInputException {
		
		Query uaQuery = pm.newQuery(UsedAddress.class);
		uaQuery.setFilter("lectureKey == key");
		uaQuery.declareParameters("String key");
		
		return (List<UsedAddress>)uaQuery.execute(l.getEncodedKey());
	}
	
	private void checkLectureObjectForInvalidChars (Lecture l) 
		throws InvalidCharacterInputException {
		
		checkForInvalidChars(l.getSubject());
		checkForInvalidChars(l.getLecturer());		
	}
	
	private void checkForInvalidChars (String input) 
		throws InvalidCharacterInputException {
		
		if (input.contains(";") || input.contains("\\"))
			throw new InvalidCharacterInputException("Error in Input! Please try again.");
	}

}
